package net.trevize.galatee;

import java.awt.AWTEvent;
import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.EventQueue;
import java.awt.Point;
import java.awt.Toolkit;
import java.awt.event.ActionEvent;
import java.awt.event.ComponentEvent;
import java.awt.event.ComponentListener;
import java.awt.event.KeyEvent;
import java.awt.event.KeyListener;
import java.awt.event.MouseAdapter;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseWheelEvent;
import java.awt.event.MouseWheelListener;
import java.awt.image.BufferedImage;
import java.net.URI;
import java.net.URL;
import java.util.HashMap;
import java.util.Vector;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.DefaultListSelectionModel;
import javax.swing.JPanel;
import javax.swing.JPopupMenu;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.KeyStroke;
import javax.swing.ListSelectionModel;
import javax.swing.SwingUtilities;
import javax.swing.border.EmptyBorder;
import javax.swing.border.LineBorder;
import javax.swing.border.MatteBorder;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableColumn;

import net.trevize.galatee.gtable.GTableCellEditor;
import net.trevize.galatee.gtable.GTableCellRenderer;
import net.trevize.galatee.gtable.GTableModel;

/**
 *
 * 
 * @author Nicolas James <nicolas.james@gmail.com> [[http://njames.trevize.net]]
 * Galatee.java - Apr 23, 2009
 */

public class Galatee extends JPanel implements ListSelectionListener,
		MouseListener, ComponentListener, KeyListener {

	private JTable table;
	private GTableModel model;
	private GTableCellRenderer renderer;
	private GTableCellEditor editor;
	private Vector<GItem> gitems;
	private int nbcol;
	private int selectedrow, selectedcol;
	private ImageLoaderThread ilt;
	public static BufferedImage imageLoadingError;

	private Vector<URI> v_uri;
	private Vector<Vector<Object>> data;

	private EventQueue eq;
	private Vector<GListener> listeners;

	private JScrollPane sp;

	//if customized imageLoadingError. (not used by default).
	private String imageLoadingErrorPath = "./gfx/imageLoadingError.jpg";

	//if customized imageLoadingError and application packed in an executable JAR.
	private URL imageLoadingErrorUrl = getClass().getClassLoader().getResource(
			imageLoadingErrorPath);

	//default value for the image thumbnail dimension.
	private Dimension gitemImageDimension = new Dimension(GalateeProperties
			.getImage_width(), GalateeProperties
			.getImage_height());

	//default value for the text description width.
	private int gitemDescriptionWidth = GalateeProperties
			.getDescription_width();

	private HashMap<String, URI> uri_index;
	private SearchPanel search_panel;
	private boolean keep_selected_cell_visible;

	private JPopupMenu popup_menu;

	private boolean description_visible;

	private PreferencesDialog preferences_dialog;

	public Galatee(Vector<URI> v_uri, Vector<Vector<Object>> data,
			Dimension imagesDim, int descriptionWidth, int nbcol) {
		super();

		/*
		 * If using a customized image for the imageLoadingError, use the following
		 * code.
		 * 
		 * However, this class is primarily coded to be used from a packed .jar,
		 * (a library), and accessing images in this case is tricky, so
		 * it's better to not use a real image and rather a built image in
		 * java, see the net.trevize.ImageLoadingError class.
		 */

		//to lauch directly (not from an executable jar).
		/*
		try {
			imageLoadingError = ImageIO.read(new File(imageLoadingErrorPath));
		} catch (IOException e) {
			e.printStackTrace();
		}
		*/

		//for launching from an executable jar.
		/*
		try {
			imageLoadingError = ImageIO.read(imageLoadingErrorUrl);
		} catch (IOException e) {
			e.printStackTrace();
		}
		*/

		/*
		 * if build a BufferedImage for the imageLoadingError.
		 */

		imageLoadingError = ImageLoadingError.getImageLoadingError(imagesDim);

		this.v_uri = v_uri;
		this.data = data;
		gitemImageDimension = imagesDim;
		gitemDescriptionWidth = descriptionWidth;

		/*
		 * :!: WARNING :!:
		 * the indicated nbcol given in parameter to the constructor
		 * is not respected if it is greater than the number of results.
		 */
		if (v_uri.size() < nbcol) {
			this.nbcol = v_uri.size();
			nbcol = this.nbcol;
		} else {
			this.nbcol = nbcol;
		}

		setLayout(new BorderLayout());
		setBorder(new EmptyBorder(0, 0, 0, 0));

		//setting the jtable.
		table = new JTable();
		model = new GTableModel();

		// filling the model.
		gitems = new Vector<GItem>();
		fillModel();

		//setting the model.
		table.setModel(model);

		//setting the renderer and the editor.
		renderer = new GTableCellRenderer(this, gitemImageDimension,
				gitemDescriptionWidth);
		editor = new GTableCellEditor(this, gitemImageDimension,
				gitemDescriptionWidth);

		//mapping some keystroke.
		((GalateeAction) action_vk_up).setEventSource(this);
		table.getActionMap().put("action_vk_up", action_vk_up);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_UP, 0, false),
				"action_vk_up");

		((GalateeAction) action_vk_right).setEventSource(this);
		table.getActionMap().put("action_vk_right", action_vk_right);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_RIGHT, 0, false),
				"action_vk_right");

		((GalateeAction) action_vk_down).setEventSource(this);
		table.getActionMap().put("action_vk_down", action_vk_down);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_DOWN, 0, false),
				"action_vk_down");

		((GalateeAction) action_vk_left).setEventSource(this);
		table.getActionMap().put("action_vk_left", action_vk_left);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_LEFT, 0, false),
				"action_vk_left");

		((GalateeAction) action_vk_enter).setEventSource(this);
		table.getActionMap().put("action_vk_enter", action_vk_enter);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false),
				"action_vk_enter");

		((GalateeAction) action_vk_plus).setEventSource(this);
		table.getActionMap().put("action_vk_plus", action_vk_plus);
		table.getInputMap().put(KeyStroke.getKeyStroke('+'), "action_vk_plus");

		((GalateeAction) action_vk_minus).setEventSource(this);
		table.getActionMap().put("action_vk_minus", action_vk_minus);
		table.getInputMap().put(KeyStroke.getKeyStroke('-'), "action_vk_minus");

		((GalateeAction) action_vk_page_up).setEventSource(this);
		table.getActionMap().put("action_vk_page_up", action_vk_page_up);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_UP, 0, false),
				"action_vk_page_up");

		((GalateeAction) action_vk_page_down).setEventSource(this);
		table.getActionMap().put("action_vk_page_down", action_vk_page_down);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_PAGE_DOWN, 0, false),
				"action_vk_page_down");

		((GalateeAction) action_vk_home).setEventSource(this);
		table.getActionMap().put("action_vk_home", action_vk_home);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_HOME, 0, false),
				"action_vk_home");

		((GalateeAction) action_vk_end).setEventSource(this);
		table.getActionMap().put("action_vk_end", action_vk_end);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_END, 0, false),
				"action_vk_end");

		((GalateeAction) action_vk_tab).setEventSource(this);
		table.getActionMap().put("action_vk_tab", action_vk_tab);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_TAB, 0, false),
				"action_vk_tab");

		((GalateeAction) action_search).setEventSource(this);
		table.getActionMap().put("action_search", action_search);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_F,
						java.awt.event.InputEvent.CTRL_MASK, false),
				"action_search");

		((GalateeAction) action_search).setEventSource(this);
		table.getActionMap().put("action_preferences_dialog",
				action_preferences_dialog);
		table.getInputMap().put(
				KeyStroke.getKeyStroke(KeyEvent.VK_P,
						java.awt.event.InputEvent.CTRL_MASK, false),
				"action_preferences_dialog");

		((GalateeAction) action_asterisk).setEventSource(this);
		table.getActionMap().put("action_asterisk", action_asterisk);
		table.getInputMap().put(KeyStroke.getKeyStroke('*'), "action_asterisk");

		table.setTableHeader(null); //no table header.
		table.setCellSelectionEnabled(true); //authorize cell selection.
		table.setDragEnabled(false); //dragging GItem is not authorized.

		/*
		 * When the setShowGrid(false) is noticed, the pixels reserved for the grid is still displayed,
		 * but not colored with the grid's color, these pixels are actually pixels of the JTable's background.
		 * 
		 * Furthermore, the completely surprising thing is the way used to paint the grid: each vertical line of 
		 * the grid is a rigth-border of a column, i.e. there are pixels reserved for the grid at the last right column,
		 * it's very ugly...  
		 * 
		 * The solution to not have a ugly pixel after the last column is to put the intercellspacing to zero.
		 */
		table.setShowGrid(false);
		table.setIntercellSpacing(new Dimension(0, 0));

		table.setRowHeight(renderer.getItemHeight());
		table.setAutoResizeMode(JTable.AUTO_RESIZE_OFF);

		// at starting time there is any selected cells.
		selectedrow = selectedcol = -1;

		// setting the selection model and the selection mode
		table.setSelectionModel(new DefaultListSelectionModel());
		table.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
		table.getColumnModel().setSelectionModel(
				new DefaultListSelectionModel());
		table.getColumnModel().getSelectionModel().setSelectionMode(
				ListSelectionModel.SINGLE_SELECTION);

		//setting the JTable's listeners.
		table.getSelectionModel().addListSelectionListener(this);
		table.getColumnModel().getSelectionModel().addListSelectionListener(
				this);
		table.addMouseListener(this);

		//setting the render of the columns.
		for (int i = 0; i < nbcol; ++i) {
			TableColumn c = table.getColumnModel().getColumn(i);
			c.setCellRenderer(renderer);
			c.setCellEditor(editor);
			c.setPreferredWidth(renderer.getItemWidth());
			c.setWidth(renderer.getItemWidth());
		}

		// authorize the creation of events.
		enableEvents(GEvent.GALATEE_EVENT);
		eq = Toolkit.getDefaultToolkit().getSystemEventQueue();
		listeners = new Vector<GListener>();

		//creating and starting the ImageLoaderThread.
		ilt = new ImageLoaderThread(this, gitemImageDimension.width,
				gitemImageDimension.height);
		ilt.start();

		//and the JTable in a JScrollPane, and the JScrollPane into this.
		sp = new JScrollPane(table);
		sp.setBorder(new LineBorder(null, 0));
		sp.setViewportView(table);
		sp.getVerticalScrollBar().setUnitIncrement(
				GalateeProperties
						.getVertical_scrollbar_unit_increment());
		sp.getHorizontalScrollBar().setUnitIncrement(
				GalateeProperties
						.getHorizontal_scrollbar_unit_increment());
		add(sp, BorderLayout.CENTER);
		sp.addComponentListener(this);
		sp.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				table.requestFocus();
			}
		});
		sp.getVerticalScrollBar().addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				keep_selected_cell_visible = false;
			}
		});
		sp.getHorizontalScrollBar().addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				keep_selected_cell_visible = false;
			}
		});
		sp.addMouseWheelListener(new MouseWheelListener() {
			@Override
			public void mouseWheelMoved(MouseWheelEvent e) {
				keep_selected_cell_visible = false;
			}
		});

		/*
		 * initializing the JPopupMenu.
		 */
		popup_menu = new JPopupMenu();
		popup_menu.setBorder(new MatteBorder(0, 15, 0, 0, Color.BLUE));

		/*
		 * at starting time the description is visible.
		 */
		description_visible = true;

		/*
		 * initialize the preferences dialog.
		 */
		preferences_dialog = new PreferencesDialog(this);
	}

	public void enableSearchFunctionality() {
		//create the uri_index used by the search functionality.
		uri_index = new HashMap<String, URI>();
		for (URI image_uri : v_uri) {
			uri_index.put(image_uri.toString(), image_uri);
		}

		//create an InMemoryDescriptionIndex.
		InMemoryDescriptionIndex index = new InMemoryDescriptionIndex(v_uri,
				data);

		//init the search panel.
		search_panel = new SearchPanel(this, index);
		add(search_panel, BorderLayout.NORTH);
		keep_selected_cell_visible = false;
	}

	public JTable getTable() {
		return table;
	}

	public void updateGItemDimension(Dimension gitemImageDimension,
			int description_width) {
		this.gitemImageDimension = gitemImageDimension;

		//update the renderer and the editor.
		if (description_visible) {
			renderer.updateDimension(gitemImageDimension, description_width);
			editor.updateDimension(gitemImageDimension, description_width);
		} else {
			renderer.updateDimension(gitemImageDimension, 0);
			editor.updateDimension(gitemImageDimension, 0);
		}

		//update the image loader thread.
		ilt.setImageWidth(gitemImageDimension.width);
		ilt.setImageHeight(gitemImageDimension.height);

		//erase the list of ever loaded images.
		for (GItem gitem : gitems) {
			gitem.setImage(null);
		}
	}

	public JPopupMenu getPopup_menu() {
		return popup_menu;
	}

	public ImageLoaderThread getImageLoaderThread() {
		return ilt;
	}

	public boolean isKeep_selected_cell_visible() {
		return keep_selected_cell_visible;
	}

	public void setKeep_selected_cell_visible(boolean keepSelectedCellVisible) {
		keep_selected_cell_visible = keepSelectedCellVisible;
	}

	private void fillModel() {
		//create columns in the JTable model.
		for (int i = 0; i < nbcol; ++i) {
			model.addColumn(new Vector<GItem>());
		}

		//use a local model, affect it to the JTable later.
		Vector<Vector<GItem>> columns = new Vector<Vector<GItem>>();
		for (int i = 0; i < nbcol; ++i) {
			columns.add(new Vector<GItem>());
		}

		for (int i = 0; i < v_uri.size(); ++i) {
			//loading the GItem.
			GItem gi = new GItem(v_uri.get(i));

			if (data != null) {
				//setting the data.
				gi.setData(data.get(i));

				//setting the description.
				String desc = data.get(i).get(0).toString();
				gi.setText(desc);
			}

			gitems.add(gi);

			//add the GItem in the model.
			columns.get(i % nbcol).add(gi);
		}

		//setting the JTable model with our local model.
		for (int i = 0; i < nbcol; ++i) {
			model.setColumn(i, columns.get(i));
		}

		Runtime.getRuntime().gc();
	}

	private void changeModelAddColumn() {

		/*
		 * we add the following condition for not adding an invisible
		 * column, i.e. not adding a column if we have no data to
		 * put in it.
		 */
		if (nbcol == v_uri.size()) {
			return;
		}

		//save the previous nbcol for updating the selectedrow and selectedcol 
		//in the new model.
		int prev_nbcol = nbcol;
		int prev_selectedrow = table.getSelectedRow();
		int prev_selectedcol = table.getSelectedColumn();

		//adding a column to the JTable model.
		model.addColumn(new Vector<GItem>());

		//update the nbcol variable.
		nbcol++;

		//clearing the model content.
		((Vector<Vector<GItem>>) model.getDataVector()).clear();

		//using a local model, affect it to the JTable later.
		Vector<Vector<GItem>> columns = new Vector<Vector<GItem>>();
		for (int i = 0; i < nbcol; ++i) {
			columns.add(new Vector<GItem>());
		}

		//insert data in the model.
		for (int i = 0; i < v_uri.size(); ++i) {
			GItem gi = gitems.get(i);
			columns.get(i % nbcol).add(gi);
		}

		//affect the model to the JTable model.
		for (int i = 0; i < nbcol; ++i) {
			model.setColumn(i, columns.get(i));
		}

		//setting again the render of the columns.
		for (int i = 0; i < nbcol; ++i) {
			TableColumn c = table.getColumnModel().getColumn(i);
			c.setCellRenderer(renderer);
			c.setCellEditor(editor);
			c.setPreferredWidth(renderer.getItemWidth());
			c.setWidth(renderer.getItemWidth());
		}

		model.fireTableDataChanged();

		Runtime.getRuntime().gc();

		//updating the selectedrow and selectedcol.
		if (prev_selectedrow != -1 && prev_selectedcol != -1) {
			int n = prev_selectedcol + prev_nbcol * prev_selectedrow;

			table.changeSelection(n / nbcol, n % nbcol, false, false);
		}

	}

	private void changeModelRemoveColumn() {

		//we can't remove all columns.
		if (nbcol == 1) {
			return;
		}

		//save the previous nbcol for updating the selectedrow and selectedcol 
		//in the new model.
		int prev_nbcol = nbcol;
		int prev_selectedrow = table.getSelectedRow();
		int prev_selectedcol = table.getSelectedColumn();

		//remove a column to the JTable model.
		table.removeColumn(table.getColumnModel().getColumn(nbcol - 1));

		//update the nbcol variable.
		nbcol--;

		//clearing the model content.
		((Vector<Vector<GItem>>) model.getDataVector()).clear();

		//it's mandatory to remove also the column id.
		Vector v = new Vector();
		for (int i = 0; i < nbcol; ++i) {
			v.add(model.getColumnName(i));
		}
		model.setDataVector(((Vector<Vector<GItem>>) model.getDataVector()), v);

		//using a local model, affect it to the JTable later.
		Vector<Vector<GItem>> columns = new Vector<Vector<GItem>>();
		for (int i = 0; i < nbcol; ++i) {
			columns.add(new Vector<GItem>());
		}

		//insert data in the model.
		for (int i = 0; i < v_uri.size(); ++i) {
			GItem gi = gitems.get(i);
			columns.get(i % nbcol).add(gi);
		}

		//affect the model to the JTable model.
		for (int i = 0; i < nbcol; ++i) {
			model.setColumn(i, columns.get(i));
		}

		//setting again the render of the columns.
		for (int i = 0; i < nbcol; ++i) {
			TableColumn c = table.getColumnModel().getColumn(i);
			c.setCellRenderer(renderer);
			c.setCellEditor(editor);
			c.setPreferredWidth(renderer.getItemWidth());
			c.setWidth(renderer.getItemWidth());
		}

		model.fireTableDataChanged();

		Runtime.getRuntime().gc();

		//updating the selectedrow and selectedcol.
		if (prev_selectedrow != -1 && prev_selectedcol != -1) {
			int n = prev_selectedcol + prev_nbcol * prev_selectedrow;

			table.changeSelection(n / nbcol, n % nbcol, false, false);
		}

	}

	public void scrollToItem(String item_uri_value) {
		int item_number = v_uri.indexOf(uri_index.get(item_uri_value));
		scrollToItem(item_number);
	}

	public void scrollToItem(int item_number) {
		keep_selected_cell_visible = true;

		int r = item_number / nbcol;
		int c = item_number % nbcol;

		/*
		 * THE NEXT LINES ARE A TEMPORARY SOLUTION, THE TRICK WILL NOT WORK ON SLOW CPUs AND HUGE IMAGES.
		 * 
		 * we push the gitem that contains the image on which we want to scroll,
		 * and we wait some milliseconds for the ImageLoaderThread.
		 */
		ilt.pushItem((GItem) table.getValueAt(r, c));

		try {
			Thread.sleep(100);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		table.changeSelection(r, c, false, false);
	}

	/***************************************************************************
	 * Managing GalateeListener.
	 **************************************************************************/

	/**
	 * for adding listener to the Galatee
	 * @param listener
	 */
	public void addGalateeListener(GListener listener) {

		listeners.add(listener);

	}

	/**
	 * this method is mandatory for event/listening mechanisms
	 */
	public void processEvent(AWTEvent e) {

		if (e instanceof GEvent) {

			GEvent gevent = (GEvent) e;

			if (gevent.getType().equals(GEvent.E_ITEM_DOUBLECLICKED)) {
				for (GListener l : listeners) {
					l.itemDoubleClicked((GEvent) e);
				}
			}

			else

			if (gevent.getType().equals(GEvent.E_SELECTION_CHANGED)) {
				for (GListener l : listeners) {
					l.selectionChanged((GEvent) e);
				}
			}

		}

	}

	/***************************************************************************
	 * implementation of MouseListener.
	 **************************************************************************/

	@Override
	public void mouseClicked(MouseEvent e) {

		if (e.getButton() == MouseEvent.BUTTON1) {
			Point p = e.getPoint();

			if (p != null) {
				int r = table.rowAtPoint(p);
				int c = table.columnAtPoint(p);
				GItem gitem = (GItem) table.getModel().getValueAt(r, c);

				if (gitem != null) {
					//managing the double click.
					if (e.getClickCount() > 1) {
						//updating the focused cell and the selected cells.
						table.setRowSelectionInterval(r, r);
						table.setColumnSelectionInterval(c, c);

						eq.postEvent(new GEvent(GEvent.E_ITEM_DOUBLECLICKED,
								this, (GItem) model.getValueAt(table
										.getSelectedRow(), table
										.getSelectedColumn())));
					}
				}//end the cell is not empty.
			}//end the point is in the JTable.

		}//end if MouseEvent.BUTTON1

	}

	@Override
	public void mouseEntered(MouseEvent e) {
	}

	@Override
	public void mouseExited(MouseEvent e) {
	}

	@Override
	public void mousePressed(MouseEvent e) {
		keep_selected_cell_visible = false;

		if (e.getButton() == MouseEvent.BUTTON1) {
			popup_menu.setVisible(false);
		}

		else

		if (e.getButton() == MouseEvent.BUTTON3) {
			//we select the item under the mouse pointer like for a MouseEvent.BUTTON1
			Point p = e.getPoint();

			if (p != null) {
				int r = table.rowAtPoint(p);
				int c = table.columnAtPoint(p);
				GItem gitem = (GItem) table.getModel().getValueAt(r, c);

				if (gitem != null) {
					//updating the focused cell and the selected cells.
					table.setRowSelectionInterval(r, r);
					table.setColumnSelectionInterval(c, c);

					eq.postEvent(new GEvent(GEvent.E_SELECTION_CHANGED, this,
							(GItem) model.getValueAt(table.getSelectedRow(),
									table.getSelectedColumn())));
				}//end the cell is not empty.
			}//end the point is in the JTable.

			//we display the JPopupMenu.
			popup_menu.show(table, e.getX(), e.getY());
		}
	}

	@Override
	public void mouseReleased(MouseEvent e) {
	}

	/***************************************************************************
	 * keystrokes actions.
	 **************************************************************************/

	private abstract class GalateeAction extends AbstractAction {

		protected Object source;

		public void setEventSource(Object source) {
			this.source = source;
		}

	}

	private Action action_vk_up = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("up");

			//if any gitem has been selected yet.
			if (table.getSelectedRow() == -1 || table.getSelectedColumn() == -1) {
				//updating selected item.
				table.setRowSelectionInterval(0, 0);
				table.setColumnSelectionInterval(0, 0);
			}

			else

			if (table.getSelectedRow() > 0) { //if we don't go out the table.
				//updating selected item.*
				table.setRowSelectionInterval(table.getSelectedRow() - 1, table
						.getSelectedRow() - 1);
			} else {

				//scrolling the table to view the selected cell.
				table.scrollRectToVisible(table.getCellRect(table
						.getSelectedRow(), table.getSelectedColumn(), true));

				return;
			}
		}

	};

	private Action action_vk_right = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("right");

			//if any gitem has been selected yet.
			if (table.getSelectedRow() == -1 || table.getSelectedColumn() == -1) {
				//updating selected item.
				table.setRowSelectionInterval(0, 0);
				table.setColumnSelectionInterval(0, 0);
			}

			else

			if (table.getSelectedColumn() < nbcol - 1) { //if we don't go out the table. 
				if (table.getModel().getValueAt(table.getSelectedRow(),
						table.getSelectedColumn() + 1) == null) { //if the cell at the right is empty.

					//scrolling the table to view the selected cell.
					table
							.scrollRectToVisible(table.getCellRect(table
									.getSelectedRow(), table
									.getSelectedColumn(), true));

					return;
				}
				//updating the selected cell.
				table.setColumnSelectionInterval(table.getSelectedColumn() + 1,
						table.getSelectedColumn() + 1);
			} else {

				//scrolling the table to view the selected cell.
				table.scrollRectToVisible(table.getCellRect(table
						.getSelectedRow(), table.getSelectedColumn(), true));

				return;
			}
		}

	};

	private Action action_vk_down = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("down");

			//if any gitem has been selected yet.
			if (table.getSelectedRow() == -1 || table.getSelectedColumn() == -1) {
				//updating selected item.
				table.setRowSelectionInterval(0, 0);
				table.setColumnSelectionInterval(0, 0);
			}

			else

			if (table.getSelectedRow() < table.getRowCount() - 1) { //if we don't go out the table.
				if (table.getModel().getValueAt(table.getSelectedRow() + 1,
						table.getSelectedColumn()) == null) { //if the cell at the bottom is empty.

					//scrolling the table to view the selected cell.
					table
							.scrollRectToVisible(table.getCellRect(table
									.getSelectedRow(), table
									.getSelectedColumn(), true));

					return;
				}

				//updating selected cell.
				table.setRowSelectionInterval(table.getSelectedRow() + 1, table
						.getSelectedRow() + 1);
			} else {

				//scrolling the table to view the selected cell.
				table.scrollRectToVisible(table.getCellRect(table
						.getSelectedRow(), table.getSelectedColumn(), true));

				return;
			}
		}

	};

	private Action action_vk_left = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("left");

			//if any gitem has been selected yet.
			if (table.getSelectedRow() == -1 || table.getSelectedColumn() == -1) {
				//updating selected item.
				table.setRowSelectionInterval(0, 0);
				table.setColumnSelectionInterval(0, 0);
			}

			else

			if (table.getSelectedColumn() > 0) { //if we don't go out the table.
				//updating selected item.
				table.setColumnSelectionInterval(table.getSelectedColumn() - 1,
						table.getSelectedColumn() - 1);
			} else {

				//scrolling the table to view the selected cell.
				table.scrollRectToVisible(table.getCellRect(table
						.getSelectedRow(), table.getSelectedColumn(), true));

				return;
			}
		}

	};

	private Action action_vk_enter = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("enter");
			if (table.getSelectedRow() >= 0 && table.getSelectedColumn() >= 0) {
				eq.postEvent(new GEvent(GEvent.E_ITEM_DOUBLECLICKED, source,
						(GItem) model.getValueAt(table.getSelectedRow(), table
								.getSelectedColumn())));
			}
		}

	};

	private Action action_vk_plus = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("plus");
			changeModelAddColumn();
		}

	};

	private Action action_vk_minus = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("minus");
			changeModelRemoveColumn();
		}

	};

	private Action action_vk_tab = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("tab");
		}

	};

	private Action action_vk_page_up = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("page_up");

			//getting the pixel coordinates of the gitem at the corner left in the viewport.
			Point gitem_at_left_bottom_corner_in_spviewport = new Point(1, sp
					.getViewport().getHeight() - 1);

			//transforming these coordinates in coordinates for the jtable.
			Point gitem_at_left_bottom_corner_in_table = SwingUtilities
					.convertPoint(sp.getViewport(),
							gitem_at_left_bottom_corner_in_spviewport, table);

			//getting the row and the column for the found coordinates.
			int gitem_bottom_row = table
					.rowAtPoint(gitem_at_left_bottom_corner_in_table);
			int gitem_left_col = table
					.columnAtPoint(gitem_at_left_bottom_corner_in_table);

			//get the number of gitems vertically displayed in the JScrollPane.
			int verticallyDisplayableGItems = sp.getViewport().getHeight()
					/ gitemImageDimension.height;

			//get the row number of the new first row (i.e. after page_up).
			int new_first_displayed_row = gitem_bottom_row
					- verticallyDisplayableGItems * 2 + 2; //the +1 is for keeping the first gitem displayed.
			if (new_first_displayed_row < 0) {
				new_first_displayed_row = 0;
			}

			//do the scroll.
			table.scrollRectToVisible(table.getCellRect(
					new_first_displayed_row, gitem_left_col, true));
		}

	};

	private Action action_vk_page_down = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("page_down");

			//getting the pixel coordinates of the gitem at the corner left in the viewport.
			Point gitem_at_left_bottom_corner_in_spviewport = new Point(1, sp
					.getViewport().getHeight() - 1);

			//transforming these coordinates in coordinates for the jtable.
			Point gitem_at_left_bottom_corner_in_table = SwingUtilities
					.convertPoint(sp.getViewport(),
							gitem_at_left_bottom_corner_in_spviewport, table);

			//getting the row and the column for the found coordinates.
			int gitem_bottom_row = table
					.rowAtPoint(gitem_at_left_bottom_corner_in_table);
			int gitem_left_col = table
					.columnAtPoint(gitem_at_left_bottom_corner_in_table);

			//get the number of gitems vertically displayed in the JScrollPane.
			int verticallyDisplayableGItems = sp.getViewport().getHeight()
					/ gitemImageDimension.height;

			//get the row number of the new first row (i.e. after page_down).
			int new_last_displayed_row = gitem_bottom_row
					+ verticallyDisplayableGItems - 2; //the -2 is for keeping the last gitem displayed.
			if (new_last_displayed_row > (table.getRowCount() - 1)) {
				new_last_displayed_row = table.getRowCount() - 1;
			}

			//do the scroll.
			table.scrollRectToVisible(table.getCellRect(new_last_displayed_row,
					gitem_left_col, true));
		}

	};

	private Action action_vk_home = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("home");

			//do the scroll.
			table.scrollRectToVisible(table.getCellRect(0, 0, true));
		}

	};

	private Action action_vk_end = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("end");

			//do the scroll.
			table.scrollRectToVisible(table.getCellRect(
					table.getRowCount() - 1, 0, true));
		}

	};

	private Action action_search = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("search");

			if (search_panel != null) {
				//make visible or unvisible the search panel.
				if (search_panel.isVisible()) {
					search_panel.setVisible(false);
				} else {
					search_panel.setVisible(true);
					search_panel.takeFocus();
				}
			}
		}

	};

	private Action action_preferences_dialog = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("preferences");

			preferences_dialog.setVisible(true);
		}

	};

	private Action action_asterisk = new GalateeAction() {

		public void actionPerformed(ActionEvent e) {
			//System.out.println("asterisk");

			if (description_visible) {
				renderer.updateDimension(gitemImageDimension, 0);
				editor.updateDimension(gitemImageDimension, 0);
				table.repaint();
				description_visible = false;
			} else {
				renderer.updateDimension(gitemImageDimension,
						gitemDescriptionWidth);
				editor.updateDimension(gitemImageDimension,
						gitemDescriptionWidth);
				table.repaint();
				description_visible = true;
			}
		}

	};

	/***************************************************************************
	 * implementation of ComponentListener.
	 **************************************************************************/

	@Override
	public void componentHidden(ComponentEvent e) {
	}

	@Override
	public void componentMoved(ComponentEvent e) {
	}

	@Override
	public void componentResized(ComponentEvent e) {
		JScrollPane sp = (JScrollPane) e.getSource();

		//		if (sp.getViewport().getSize().width > (nbcol + 1) * renderer.getItemWidth()) {
		//			while (sp.getViewport().getSize().width > (nbcol + 1) * renderer.getItemWidth()) {
		//				changeModelAddColumn();
		//			}
		//			return;
		//		}

		//		if (sp.getViewport().getSize().width < (nbcol) * renderer.getItemWidth()) {
		//			while (sp.getViewport().getSize().width < (nbcol) * renderer.getItemWidth()) {
		//				changeModelRetrieveColumn();
		//			}
		//			return;
		//		}
	}

	@Override
	public void componentShown(ComponentEvent e) {
	}

	/***************************************************************************
	 * implementation of ListSelectionListener.
	 **************************************************************************/

	@Override
	public void valueChanged(ListSelectionEvent e) {
		synchronized (this) {
			if (!e.getValueIsAdjusting()) {
				/*
				 * verifying that it's a true real "new selection".
				 * 
				 * (the contrary could happen because we have a ListSelectionListener
				 * for the rows and another one for the columns).
				 */
				if (selectedrow == table.getSelectedRow()
						&& selectedcol == table.getSelectedColumn()) {
					return;
				}

				int r = table.getSelectedRow();
				int c = table.getSelectedColumn();

				/*
				 * some ListSelectionEvent are thrown with no selection...
				 */
				if (r == -1 || c == -1) {
					return;
				}

				//scrolling the table to view the selected cell.
				table.scrollRectToVisible(table.getCellRect(r, c, true));

				/*
				 * we edit the current cell (for having the JTextArea in the cell, and thus
				 * allowing the selection of the text with the mouse for copy/paste filepath etc.).
				 */
				table.editCellAt(r, c);

				//creating the new GEvent.E_SELECTION_CHANGED event.
				eq.postEvent(new GEvent(GEvent.E_SELECTION_CHANGED, this,
						(GItem) model.getValueAt(r, c)));

				/*
				 * updating the selectedcell.
				 */
				selectedrow = r;
				selectedcol = c;
			}
		}
	}

	/***************************************************************************
	 * implementation of KeyListener.
	 **************************************************************************/

	@Override
	public void keyPressed(KeyEvent e) {
		keep_selected_cell_visible = false;
	}

	@Override
	public void keyReleased(KeyEvent e) {
	}

	@Override
	public void keyTyped(KeyEvent e) {
	}

}
